Skip to content

feat: shared components — reusable, linked pipeline configs#82

Merged
TerrifiedBug merged 19 commits intomainfrom
reusable-components-6cb
Mar 10, 2026
Merged

feat: shared components — reusable, linked pipeline configs#82
TerrifiedBug merged 19 commits intomainfrom
reusable-components-6cb

Conversation

@TerrifiedBug
Copy link
Copy Markdown
Owner

Summary

  • Shared Components — environment-scoped, versioned component configurations that can be linked into multiple pipelines. Edit in one place, all linked pipelines surface "update available" indicators. Pipeline owners explicitly accept updates (review-first model).
  • Library page — renamed from /templates to /library with sub-navigation for Templates and Shared Components. Full CRUD management for shared components including create-from-scratch, create-from-node, edit, delete, and linked pipeline tracking.
  • Pipeline editor integration — Shared tab in component palette with drag-to-add, purple visual treatment for linked nodes, read-only config with unlink option, stale update banner with accept button, and "Save as Shared Component" context menu action.
  • Pipeline list — amber "Updates available" badge on pipelines with stale linked components.
  • Edge cases — clone preserves links, promote/templates strip links, discard restores links, delete shared component gracefully unlinks all nodes.

Test plan

  • Create a shared component from the Library page (New Shared Component flow)
  • Create a shared component from a pipeline node (right-click > Save as Shared Component)
  • Drag a shared component from the Shared palette tab onto the canvas — verify purple border, read-only config
  • Edit the shared component in the Library — verify linked pipelines show "Update available"
  • Accept update on a linked node — verify config updates and stale indicator clears
  • Unlink a node — verify config becomes editable and shared indicator disappears
  • Delete a shared component — verify linked nodes become standalone with config preserved
  • Clone a pipeline with linked nodes — verify links are preserved
  • Promote a pipeline — verify shared component links are stripped
  • Save a pipeline as template — verify shared component links are stripped
  • Verify /templates redirects to /library/templates
  • Run npx tsc --noEmit, pnpm lint, pnpm build — all pass

- Detail panel shows "Open in Library" link on shared component banner
- Component palette shared tab includes kind filter buttons (All/Source/Transform/Sink)
- Verify environment ownership on getById, update, delete (prevents IDOR)
- Validate cross-environment linking in linkExisting
- Use atomic version increment to prevent race conditions
- Move createFromNode uniqueness check inside transaction (TOCTOU fix)
- Fix operator precedence in discardChanges node mapping
- New shared-components.md covering full feature lifecycle
- Update templates.md to reflect Library page rename and shared link stripping
- Update pipeline-editor.md with Shared palette tab, context menu, and linked node detail panel
- Update pipelines.md with stale shared component badge
- Add Shared Components entry to SUMMARY.md table of contents
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR introduces Shared Components — environment-scoped, versioned pipeline component configurations that can be linked into multiple pipelines — along with a restructured Library page (replacing /templates with /library) that hosts both Templates and the new Shared Components section.

Key changes:

  • New SharedComponent Prisma model with (environmentId, name) unique constraint, onDelete: SetNull FK on PipelineNode, and a version counter that increments on config edits.
  • sharedComponentRouter — full CRUD (create, createFromNode, update, delete, acceptUpdate, acceptUpdateBulk, unlink, linkExisting). All procedures correctly apply withTeamAccess and withAudit middleware. saveGraph validates that all sharedComponentIds belong to the pipeline's own environment before writing.
  • Flow store gains three new actions: patchNodeSharedData (no history push, intentionally amends the preceding addNode snapshot), acceptNodeSharedUpdate (pushes snapshot), unlinkNode (pushes snapshot). The fingerprint computation correctly excludes transient display fields (sharedComponentName, sharedComponentLatestVersion).
  • Pipeline editor shows purple nodes/banners for linked components, amber update banners for stale nodes, and a right-click "Save as Shared Component" context menu action. save-shared-component-dialog.tsx correctly syncs the Zustand store after createFromNode succeeds.
  • Copy/promote logiccopyPipelineGraph gains a stripSharedComponentLinks flag; promote passes true, clone does not. Template creation strips links via the templateNodeSchema Zod schema (which simply omits sharedComponentId).
  • Pipeline list surfaces an amber "Updates available" badge with a tooltip listing the stale component names.

Minor issues found:

  • acceptUpdateMutation and unlinkMutation in detail-panel.tsx are missing onError toast handlers, causing silent failure when the server returns an error.
  • New /library/shared-components/ pages (list, detail, new) and the library layout are missing ErrorBoundary protection, per project conventions.

Confidence Score: 4/5

  • PR is safe to merge — all previously flagged security issues have been addressed and the core feature logic is correct.
  • The cross-environment injection vectors, TOCTOU race conditions, and missing store syncs called out in previous review rounds are all resolved in this iteration. The remaining findings are both minor: missing onError toast handlers (silent failure) and missing ErrorBoundary wrappers on the new library pages. Neither is a data-loss or security risk, but they affect user experience and crash resilience.
  • src/components/flow/detail-panel.tsx — missing onError handlers on acceptUpdateMutation and unlinkMutation. New library pages under src/app/(dashboard)/library/shared-components/ — missing ErrorBoundary protection.

Important Files Changed

Filename Overview
src/server/routers/shared-component.ts New tRPC router for all shared component CRUD operations — create, createFromNode, update, delete, acceptUpdate, acceptUpdateBulk, unlink, linkExisting. All procedures correctly apply withTeamAccess and withAudit middleware. The previously flagged cross-environment injection and transaction issues are fixed. Minor: acceptUpdate and unlink mutations in the companion UI components are missing onError handlers.
src/server/routers/pipeline.ts saveGraph now validates that all sharedComponentIds belong to the pipeline's own environment before writing to DB. Promote path passes stripSharedComponentLinks: true to copyPipelineGraph. nodesSnapshot in createVersion includes sharedComponentId/Version fields for correct discard behavior. Previously flagged issues are resolved.
src/components/flow/flow-canvas.tsx Drag-and-drop from Shared palette uses patchNodeSharedData store action (replacing raw setState). patchNodeSharedData intentionally skips undo history since the addNode call immediately before already pushed a snapshot, keeping the two as one logical undo unit. Still relies on nodes[nodes.length-1] assumption from prior review.
src/components/flow/detail-panel.tsx Correctly wires acceptUpdate and unlink mutations with store sync (acceptNodeSharedUpdate/unlinkNode). Shows purple shared banner, stale update amber banner, and Unlink button. Both new mutations are missing onError toast handlers — failures are silent to the user.
src/stores/flow-store.ts Three new store actions: patchNodeSharedData (no history push — intentional), acceptNodeSharedUpdate (pushes snapshot), unlinkNode (pushes snapshot). computeFlowFingerprint excludes sharedComponentName/LatestVersion from dirty tracking. FlowNodeData interface extended with shared component fields.
src/server/services/copy-pipeline-graph.ts New stripSharedComponentLinks option correctly nulls sharedComponentId and sharedComponentVersion when set. Clone (no flag) preserves links; promote (flag=true) strips them. Template creation already strips links via Zod schema (templateNodeSchema has no sharedComponentId field).
prisma/schema.prisma New SharedComponent model with unique constraint on (environmentId, name), indexed environmentId, and onDelete: SetNull FK on PipelineNode. PipelineNode gains sharedComponentId and sharedComponentVersion fields. Schema is well-structured for the feature.
src/app/(dashboard)/library/shared-components/[id]/page.tsx Detail/edit page for a shared component. Full CRUD (update name/description/config, delete with confirmation). Linked pipelines shown with up-to-date vs stale badge. Missing ErrorBoundary protection per project conventions.

Entity Relationship Diagram

%%{init: {'theme': 'neutral'}}%%
erDiagram
    Environment {
        string id PK
        string name
    }
    SharedComponent {
        string id PK
        string name
        string componentType
        ComponentKind kind
        Json config
        int version
        string environmentId FK
        DateTime createdAt
        DateTime updatedAt
    }
    PipelineNode {
        string id PK
        string pipelineId FK
        string componentType
        ComponentKind kind
        Json config
        string sharedComponentId FK "nullable, SetNull on delete"
        int sharedComponentVersion "nullable"
    }
    Pipeline {
        string id PK
        string environmentId FK
        string name
    }

    Environment ||--o{ SharedComponent : "scopes"
    Environment ||--o{ Pipeline : "contains"
    Pipeline ||--o{ PipelineNode : "has"
    SharedComponent ||--o{ PipelineNode : "linked to (0..*)"
Loading

Comments Outside Diff (2)

  1. src/components/flow/detail-panel.tsx, line 2209-2233 (link)

    Silent failure on accept/unlink mutations

    Both acceptUpdateMutation and unlinkMutation are missing onError handlers. If either call fails (network error, DB error, node deleted by another session), the user sees no feedback — the button appears to do nothing.

    save-shared-component-dialog.tsx in this same PR already sets the correct pattern:

    onError: (error) => {
      toast.error(error.message);
    },

    Adding onError to both mutations here would keep user-facing error reporting consistent across the shared-component flow.

  2. src/app/(dashboard)/library/shared-components/[id]/page.tsx, line 455 (link)

    Missing ErrorBoundary protection on new library pages

    Per project conventions, new dashboard pages should be wrapped in an ErrorBoundary so that tRPC errors or unexpected exceptions render a graceful fallback rather than crashing the entire layout shell.

    The same issue applies to the other three new pages introduced in this PR:

    • src/app/(dashboard)/library/shared-components/page.tsx (line 1)
    • src/app/(dashboard)/library/shared-components/new/page.tsx (line 1)
    • src/app/(dashboard)/library/layout.tsx (line 1)

    Wrapping the page content (or the layout's <main>) with an ErrorBoundary at each entry point would bring these pages in line with the rest of the dashboard.

    Rule Used: ## Code Style & Conventions

    TypeScript Conven... (source)

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Last reviewed commit: 7b7e356

…e name check in transaction

- createFromNode now verifies the pipeline belongs to the claimed environmentId
- update procedure wraps lookup + name-conflict check + write in a transaction
  to prevent TOCTOU race on concurrent renames
@TerrifiedBug
Copy link
Copy Markdown
Owner Author

@greptile fixed.

  1. createFromNode — now validates node.pipeline.environmentId === input.environmentId to prevent
    cross-environment config leakage
  2. update — lookup, name-conflict check, and write all wrapped in $transaction to prevent TOCTOU race
    producing unhandled P2002 errors

…te in transaction

- saveGraph now validates all sharedComponentIds belong to the pipeline's
  environment before persisting, preventing cross-environment injection
- create procedure wraps duplicate-name check in a transaction to match
  createFromNode pattern and prevent TOCTOU race (P2002 → clean CONFLICT)
@TerrifiedBug
Copy link
Copy Markdown
Owner Author

@greptile fixed.

  1. saveGraph cross-environment injection — now validates all sharedComponentId values in the payload belong
    to the pipeline's environment before persisting
  2. create TOCTOU race — duplicate-name check + create wrapped in $transaction, matching the createFromNode
    pattern

…/redo

- Add patchNodeSharedData, acceptNodeSharedUpdate, unlinkNode store actions
- acceptUpdate/unlink mutations now sync the flow store on success,
  preventing saveGraph from reverting DB changes with stale in-memory data
- Shared component drop uses patchNodeSharedData instead of raw setState,
  preserving linkage across undo/redo cycles
- acceptUpdate endpoint returns decrypted config + version for client sync
- saveGraph validates that all sharedComponentIds exist (not just env match)
@TerrifiedBug
Copy link
Copy Markdown
Owner Author

@greptile fixed

  1. Mutations reverted on save — acceptUpdate and unlink now sync the Zustand flow store via dedicated
    acceptNodeSharedUpdate and unlinkNode actions, so saveGraph writes the correct values.
  2. Undo/redo breaks shared linkage — Shared component drop now uses patchNodeSharedData store action
    instead of raw setState, so the patched data is included in the node state that addNode's history snapshot
    captures.
  3. saveGraph FK gap — Now validates all sharedComponentId values were found in the DB before checking
    environment ownership, producing a clean BAD_REQUEST instead of an unhandled FK constraint error.

Canvas now immediately shows purple shared treatment after creating a
shared component from a node, without requiring a page reload.
@TerrifiedBug
Copy link
Copy Markdown
Owner Author

@greptile fixed.

The SaveSharedComponentDialog now calls patchNodeSharedData in onSuccess to sync the Zustand store — canvas immediately shows the purple border, read-only config, and shared banner without needing a page reload.

@TerrifiedBug TerrifiedBug merged commit d609651 into main Mar 10, 2026
12 checks passed
@TerrifiedBug TerrifiedBug deleted the reusable-components-6cb branch March 10, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant